#!/usr/bin/env python
# -*- coding: utf-8 -*-

__author__ = 'James Iter'
__date__ = '15/6/23'
__contact__ = 'james.iter.cn@gmail.com'
__copyright__ = '(c) 2015 by James Iter.'

import json
from functools import wraps

from flask import make_response
from flask.wrappers import Response
from werkzeug.utils import import_string, cached_property
import datetime, time

from models.initialize import *
from models import Rules


class Utils(object):

    @staticmethod
    def dumps2response(func):
        """
        视图装饰器
        http://dormousehole.readthedocs.org/en/latest/patterns/viewdecorators.html
        """
        @wraps(func)
        def _dumps2response(*args, **kwargs):
            ret = func(*args, **kwargs)

            if func.func_name != 'r_before_request' and ret is None:
                ret = dict()
                ret['state'] = ji.Common.exchange_state(20000)

            if isinstance(ret, dict) and 'state' in ret:
                response = make_response()
                response.data = json.dumps(ret, ensure_ascii=False)
                response.status_code = int(ret['state']['code'])
                return response

            if isinstance(ret, Response):
                return ret

        return _dumps2response

    @staticmethod
    def dsl_to_sql(name=None, dsl=None):
        # DSL for Domain Specific language
        args_rules = [
            Rules.DSL.value,
            (basestring, 'name', objects_model['rules'].keys())
        ]
        ji.Check.previewing(args_rules, locals())

        ele_s = dsl.split('__')
        args_rules = [
            Rules.DSL_ELEMENT_S.value
        ]
        ji.Check.previewing(args_rules, locals())

        def get_fit_statement():
            ele_type = objects_model['rules'][name][ele_s[0]][0]
            if ele_type == basestring:
                return ''.join(['"', ele_s[2], '"'])
            elif ele_type == (int, long):
                return ele_s[2]
            else:
                raise TypeError(''.join(['unknown type ', str(ele_type)]))

        def get_fit_statement_with_tilde():
            # 针对IN的特殊优化
            ele_type = objects_model['rules'][name][ele_s[0]][0]
            ele_s_with_in = []

            if ele_type == basestring:
                for ele_with_in in ele_s[2].split('~'):
                    ele_s_with_in.append(''.join(['"', ele_with_in, '"']))

            elif ele_type == (int, long):
                ele_s_with_in = ele_s[2].split('~')

            else:
                raise TypeError(''.join(['unknown type ', str(ele_type)]))

            return ''.join(['(', ','.join(ele_s_with_in), ')'])

        operator = None
        fit_statement_list = None
        if ele_s[1] in ['eq', 'gt', 'lt', 'ne']:
            fit_statement_list = [ele_s[0], get_fit_statement()]
            if ele_s[1] == 'eq':
                operator = ' = '
            elif ele_s[1] == 'gt':
                operator = ' > '
            elif ele_s[1] == 'lt':
                operator = ' < '
            elif ele_s[1] == 'ne':
                operator = ' != '

        elif ele_s[1] == 'in':
            operator = ' IN '
            fit_statement_list = [ele_s[0], get_fit_statement_with_tilde()]

        elif ele_s[1] == 'order_by' or ele_s[1] == 'order_by_desc':
            args_rules = [
                (basestring, 'field', objects_model['rules'][name].keys())
            ]
            ji.Check.previewing(args_rules, {'field': ele_s[2]})

            operator = ' '
            fit_statement_list = ['order by', ele_s[2]]
            if ele_s[1] == 'order_by_desc':
                fit_statement_list = ['order by', ele_s[2], 'desc']

        elif ele_s[1] == 'limit':
            if ele_s[2].isdigit() and 1 <= int(ele_s[2]) <= 1000:
                operator = ' '
                fit_statement_list = ['limit', ele_s[2]]
            else:
                ret = dict()
                ret['state'] = ji.Common.exchange_state(41203)
                ret['state']['sub']['zh-cn'] = ''.join([ret['state']['sub']['zh-cn'], u' 预期取值范围 整数1~1000, 收到 ',
                                                        ele_s[2]])
                raise ji.PreviewingError(json.dumps(ret))

        elif ele_s[1] == 'range':
            range_num_s = []
            for i, range_num in enumerate(ele_s[2].split('~')):
                if not range_num.isdigit() or not 0 <= int(range_num):
                    raise TypeError(''.join(['unknown type ', ele_s[1]]))

                range_num_s.append(range_num)
                if i >= 1:
                    break

            operator = ' '
            fit_statement_list = ['range', range_num_s]
            return operator.join(['range', str(range_num_s)]), fit_statement_list

        else:
            raise TypeError(''.join(['unknown type ', ele_s[1]]))

        return operator.join(fit_statement_list), fit_statement_list

    @staticmethod
    def get_cycle_time_length(cycle_unit=None):
        # 获取最后一个周期的时间长度,单位秒
        args_rules = [
            Rules.CYCLE_UNIT.value,
        ]
        ji.Check.previewing(args_rules, locals())

        if cycle_unit.lower() == 'd':
            return 86400
        elif cycle_unit.lower() == 'w':
            return 86400 * 7
        else:
            month_begin = datetime.date.today().replace(day=1)
            this_month_begin_ts = time.mktime(month_begin.timetuple())
            if month_begin.month > 1:
                last_month_begin_ts = time.mktime(month_begin.replace(month=month_begin.month - 1).timetuple())
            else:
                last_month_begin_ts = time.mktime(month_begin.replace(year=month_begin.year - 1, month=12).timetuple())
            return int(this_month_begin_ts - last_month_begin_ts)

    @staticmethod
    def get_the_cycle_begin_ts(cycle_unit=None, offset=0):
        # 获取最后一个周期的起始时间戳
        args_rules = [
            Rules.CYCLE_UNIT.value,
            Rules.OFFSET.value,
        ]
        ji.Check.previewing(args_rules, locals())

        if cycle_unit.lower() == 'd':
            return int(time.mktime((datetime.date.today() - datetime.timedelta(days=offset)).timetuple()))

        elif cycle_unit.lower() == 'w':
            _today = datetime.date.today()
            return int(time.mktime((_today - datetime.timedelta(weeks=offset, days=_today.timetuple().tm_wday)
                                    ).timetuple()))

        else:
            month_begin = datetime.date.today().replace(day=1)
            offset_year = offset / 12
            offset_month = offset % 12
            last_month_begin_ts = time.mktime(month_begin.replace(year=month_begin.year - offset_year,
                                                                  month=month_begin.month - offset_month).timetuple())
            return int(last_month_begin_ts)

    @staticmethod
    def ts_to_date(timestamp=None):
        args_rules = [
            Rules.TIMESTAMP.value,
        ]
        ji.Check.previewing(args_rules, locals())
        structured_time = time.localtime(timestamp)
        return structured_time.tm_year * 10000 + structured_time.tm_mon * 100 + structured_time.tm_mday

    @staticmethod
    def generate_id_by(type_):
        return r.hincrby(app.config['IDsKeeper'], type_, 1)

    @staticmethod
    def delete_id_keeper_by(type_):
        return r.hdel(app.config['IDsKeeper'], type_)

    @staticmethod
    def get_id_of_max_by(type_):
        return long(r.hget(app.config['IDsKeeper'], type_))


class LazyView(object):
    """
    惰性载入视图
    http://dormousehole.readthedocs.org/en/latest/patterns/lazyloading.html
    """

    def __init__(self, import_name):
        self.__module__, self.__name__ = import_name.rsplit('.', 1)
        self.import_name = import_name

    @cached_property
    def view(self):
        return import_string(self.import_name)

    def __call__(self, *args, **kwargs):
        return self.view(*args, **kwargs)


def add_rule(blueprint, rule, view_func=None, **options):
    blueprint.add_url_rule(rule=rule, view_func=LazyView(''.join(['views.', view_func])), **options)
